/*
 * File:   p_queue.c
 * Author: dev
 *
 * This queue (well, not technically a queue, more like a cache) that we
 * will store packets while we wait for arps, all interfaces will have this
 * queue, and will hold outbout packets for that interface. all of the packets
 * will be waiting for some hardware address for a given IP.
 *
 * Created on March 15, 2011, 8:15 PM
 */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <stdint.h>

#include "sr_if.h"
#include "sr_rt.h"
#include "sr_rt.h"
#include "if_tt.h"


#include "sr_router.h"
#include "sr_protocol.h"
#include "p_queue.h"

/*---------------------------------------------------------------------
 * Method: queue_packet
 *
 * Adds a packet to the list, drops the packet if it's greater than
 * the size of the queue, or if we've already seen 5 packets also going to the
 * same IP (meaning 5 unreplied arps to that IP)
 *
 * IMPORTANT:
 * always call check_queue_status() and act accordingly before use.
 *
 *---------------------------------------------------------------------*/
void queue_packet(struct sr_if* iface, uint8_t* packet,unsigned int len)
{
    assert(iface);
    struct p_queue* queue_walker = 0;
    struct ip* ip_hdr = 0;
    
    ip_hdr = (struct ip*)(packet+ sizeof(struct sr_ethernet_hdr*));

    /* Just queue if it's empty */
    if(iface->queue == 0)
    {
        iface->queue = (struct p_queue*) malloc(sizeof(struct p_queue));
        assert(iface->queue);
        iface->queue->next = 0;
        iface->queue->prev = 0;
        iface->queue->packet = packet;
        iface->queue->len = len;
        return;
    }
    queue_walker = iface->queue;

    /* work to the end of the queue
     * ALWAYS CALL check_queue_status
     */
    while(queue_walker->next)
    {queue_walker = queue_walker->next;}

    /*otherwise we're at the end of the queue, and we have space!*/
    queue_walker->next = (struct p_queue*)malloc(sizeof(struct p_queue));
    assert(queue_walker->next);

    queue_walker->next->prev = queue_walker;
    queue_walker = queue_walker->next;
    queue_walker->next = 0;
    queue_walker->packet = packet;
    queue_walker->len = len;

} /* -- queue_packet -- */

/*---------------------------------------------------------------------
 * Method: check_queue
 *
 * Checks the queue to see if there is an IP entry in the translation
 * table so that we can fill in the hardware address and send the packet.
 *
 * This should be called everytime the interface recieves an arp packet
 *---------------------------------------------------------------------*/
void check_queue(struct sr_instance* sr,struct sr_if* iface)
{
    assert(sr);
    assert(iface);
    
    struct p_queue* queue_walker = 0;
    struct ip* ip_hdr = 0;
    struct sr_ethernet_hdr* e_hdr = 0;
    struct if_tt* translation;
    unsigned int dropped = 0;

    if(iface->queue == 0)
    {
        return;
    }
    queue_walker = iface->queue;
    while(queue_walker)
    {
        /*look at the IP destination of the packet and see if we have
         * an entry in the table.
         */
        ip_hdr = (struct ip*)(queue_walker->packet +  sizeof(struct sr_ethernet_hdr));
        translation = if_find_trans(iface,(uint32_t*)&ip_hdr->ip_dst.s_addr);
        if (translation!=0)
        {
            /* Success! a match is found, copy the hardware address to the packet
             * and send send send!
             */
            e_hdr = (struct sr_ethernet_hdr*)(queue_walker->packet);
            memcpy(&e_hdr->ether_dhost,&translation->hrd_addr,ETHER_ADDR_LEN);
            //printf("Sending queued packet\n");
            sr_send_packet(sr, queue_walker->packet, queue_walker->len, iface->name);
            queue_walker = drop_singl_packet(iface, queue_walker);
            dropped = 1;
        }
        if(dropped == 0)
            queue_walker = queue_walker->next;
        dropped = 0;
    }

}/* -- check_queue -- */

/*---------------------------------------------------------------------
 * Method: drop_packets
 *
 * deletes the packets and packet queue entries for any entry with the
 * given IP address.
 *
 * this will be called everytime we have more than 5 packets waiting for an arp
 *---------------------------------------------------------------------*/
void drop_packets(struct sr_if* iface, struct in_addr* dest_addr)
{
    assert(iface);
    struct p_queue* queue_walker = 0;
    struct p_queue* pkt_to_drop = 0;
    struct ip* ip_hdr = 0;
    unsigned int drop = 0;

    /*Will probably never have this, but just in case*/
    if(iface->queue == 0)
    {   return;    }

    queue_walker = iface->queue;
    while(queue_walker)
    {
        ip_hdr = (struct ip*)(queue_walker->packet + sizeof(struct sr_ethernet_hdr*));
        if(memcmp(&ip_hdr->ip_dst.s_addr, dest_addr, ETHER_ADDR_LEN)==0)
        {
            if(queue_walker->prev == 0)
            {
                if(queue_walker->next == 0)
                {
                    iface->queue = 0;
                }
                else
                {
                    queue_walker->prev->next = queue_walker->next;
                    queue_walker->next->prev = 0;
                }
            }
            else
            {
                if(queue_walker->next == 0)
                {
                    queue_walker->prev->next = 0;
                    queue_walker->next->prev = queue_walker->prev;
                }
                else
                {
                    queue_walker->prev->next = queue_walker->next;
                    queue_walker->next->prev = queue_walker->prev;
                }
            }
            
            pkt_to_drop = queue_walker;
            queue_walker = queue_walker->next;
            free(pkt_to_drop->packet);
            free(pkt_to_drop);
            drop = 1;
        }
        if(drop == 0)
            queue_walker = queue_walker->next;
        drop=0;
    }
    return;
} /* -- drop_packets -- */

/*---------------------------------------------------------------------
 * Method: drop_singl_packet
 *
 * removes a single packet from the queue, used after we have sent off a packet
 * and need to pop it from the queue.
 *---------------------------------------------------------------------*/
struct p_queue* drop_singl_packet(struct sr_if* iface, struct p_queue* entry_to_drop)
{
    assert(iface);
    struct p_queue* next = entry_to_drop->next;

    if(entry_to_drop->prev == 0)
    {
        if(entry_to_drop->next == 0)
        {
            iface->queue = 0;
        }
        else
        {
            iface->queue = entry_to_drop->next;
            entry_to_drop->next->prev = 0;
        }
    }
    else
    {
        if(entry_to_drop->next == 0)
        {
            entry_to_drop->prev->next = 0;
        }
        else
        {
            entry_to_drop->prev->next = entry_to_drop->next;
            entry_to_drop->next->prev = entry_to_drop->prev;
        }
    }

    free(entry_to_drop->packet);
    free(entry_to_drop);
    return next;
} /* -- drop_singl_packet -- */


/*---------------------------------------------------------------------
 * Method: check_queue_status
 *
 * checks the queue for two things. 1) is there space 2) how many other
 * packets are also going to the same IP address.
 *
 * returns
 * 0 = all clear
 * -1 = no space in queue
 * 1-5 = # of occurences in the queue.
 *---------------------------------------------------------------------*/
int check_queue_status(struct sr_if* iface, struct in_addr* dest_addr)
{
    assert(iface);
    struct p_queue* queue_walker = 0;
    struct ip* q_ip_hdr = 0;

    int occurance = 0;
    int size = 0;

    /* Just queue if it's empty */
    if(iface->queue == 0)
    {
        return 0;
    }
    queue_walker = iface->queue;

    /* While working to the end of the queue, count how many times we see
     * packets going to the same IP. any more than the allowed
     */
    while(queue_walker->next && size < MAX_QUEUE_LIMIT)
    {
        q_ip_hdr = (struct ip*)(queue_walker->packet + sizeof(struct sr_ethernet_hdr*));
        if( memcmp( &dest_addr->s_addr, &q_ip_hdr->ip_dst.s_addr, 4) == 0 )
        {
            occurance+=1;
            if(occurance == 5)
            {
                return occurance;
            }
        }
        queue_walker = queue_walker->next;
        size+=1;
    }
    /*return -1 if we're at the max limit return limit message*/
    if(size == MAX_QUEUE_LIMIT)
        return -1;

    return occurance;
}/* -- check_queue_status --*/


